package zbl.c0.lexer;

import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;
import java.util.Hashtable;

import zbl.c0.error.Errors;
import zbl.c0.error.Perror;


/**
 * 		词法分析类，成功则返回对应的Token类，如果读取到文件结束符，则返回
 * tag == -1的Token ； 如果中途发生错误则打印出错信息返回null;
 * 
 * @author 朱伯龙
 * 
 */
public class Lexer {

	 private static int curLine;				//	标记当前行号						从 0 计数
	 private static int curNum;					//	标记当前行对应的单词号			从 0 计数
	 private static int totalNum;				//  记录分析到的全部词法单元的数目          从 0 计数
	 
	 private char peek;									//  保存读指针当前读取的字符
	 Hashtable<String ,Word> wordsTable;		//  存储 预留的以及读取到的 词法单元（保留字, 特殊符号, 标识符）
	 
	 private static FileReader rf;				//  文件读写流指针
	 private static PushbackReader codeReader;	//  代码读写流指针，允许将字符推回到流的字符流
	 
	 
	 /**
	  * 	重写 Lexer 类的构造函数，初始化词法分析器的一些静态变量
	  * 	加载 保留字 和 特殊符号 到wordsTable中，便于查找返回
	  */
	 public Lexer(){
		 
		//#######################初始化过程###########################
			
		   	curLine = 1;						//  初始化当前行号为	      1   
		   	curNum = 0;							//  初始化当前行的单词号为    0
			totalNum = 0;						//  初始化词法单元计数器为    0
			
			
			peek = ' ';							//  初始化未读信息前，当前字符值为 ' '（空格，分析时忽略）
			wordsTable = new Hashtable<String ,Word>();	//  为wordsTable分配存储空间
			
		//---------------  加载保留字词法单元到wordsTable中   -------------
			
			reserve( new Word("const", Tag.CONST) );
			reserve( new Word("int", Tag.INT) );
	        reserve( new Word("void", Tag.VOID) );
	        reserve( new Word("main", Tag.MAIN) );
	        reserve( new Word("if", Tag.IF) );
	        reserve( new Word("do", Tag.DO) );
	        reserve( new Word("while", Tag.WHILE) );
	        reserve( new Word("return", Tag.RETURN) );
	        reserve( new Word("scanf", Tag.SCANF) );
	        reserve( new Word("printf", Tag.PRINTF) );
	        
	   //___________________  加载保留字完毕    _______________________________
	      
	 }
	 
	 /**
	  * 	整个词法分析程序的核心部分，分析动作。将分析到的结果以
	  * 词法单元的形式返回
	  * 
	  * @return 词法分析器当前分析到的词法单元
	  */
	 public Token scan()throws IOException{
		 
		 //********   跳过空白信息， 如果读取到'\n'字符则更新行号、以及当前行的词法单元号
		 for( ; ; readNext()){
			 if(peek == ' ' || peek == '\t' || peek == '\r') continue;
			 else if(peek == '\n'){
				 curLine++;
				 curNum = 0;
			 }
		     else {
		    	 curNum++;
		    	 totalNum++;
		    	 break;
		     }
		 }
		 
		 switch(peek){							// 因为在readch方法调用的时候已经预读过一次
		 										// 所以条件不成立时不可再置peek = ' '
		 										// 否则会丢失预读的字符
			 case '<': 
				 if( readch('=') ) return Word.le;  
				 else {
					 return Token.smaller;
				 }
			 case '>':
				 if( readch('=') ) return Word.ge;	
				 else {
					 return Token.larger;
				 }
			 case '=': 
				 if( readch('=') )
					 return Word.eq;
				 else { 
					 return Token.assign;
				 }
			 case '!':
				 if( readch('=') )
					 return Word.ne;
				 else {
					 									// 报错！！！ 读取到非法字符
					 new Perror(Errors.LexInvalidChar).printErrorInfo(getLineno(), getWordno());
					 peek = ' ';						// 便于继续分析	
					 return null;						// 出错返回信息
				 }
			 case '{': peek = ' '; return Token.l_b_bracket;
			 case '}': peek = ' '; return Token.r_b_bracket;
			 case '(': peek = ' '; return Token.l_bracket;
			 case ')': peek = ' '; return Token.r_bracket;
			 case ';': peek = ' '; return Token.semicolon;
			 case ',': peek = ' '; return Token.comma;
			 case '+': peek = ' '; return Token.plus;
			 case '-': peek = ' '; return Token.minus;
			 case '*': peek = ' '; return Token.mul;
			 case '/': peek = ' '; return Token.divide;
		 }
		 
		 if( Character.isDigit(peek) ){		// 识别不包含正负号的整数     返回	Num
			 int value=0;
			 if(peek == '0'){
				 peek = ' ';				//  为下一次取词做准备
				 return new Num(value);				//  是 '0' 则直接返回 0 构建的Num对象
			 } 
			 else{
				 do{
					 if((value = 10*value + Character.digit(peek, 10))<0)
					 {		
																// 报错！！！整数超过界限
						 new Perror(Errors.LexIntegerOutOfBoundary).printErrorInfo(getLineno(), getWordno());
						 peek = ' ';							// 便于继续分析	
						 return null;							// 出错返回信息
					 }
					 readNext();
			     }while( Character.isDigit(peek) );	// 否则， 计算数字对应的整型值，由此构建Num对象
				 return new Num(value);
			 }
	     }
		 
		 if( peek == '"' ){					//  识别字符串常量     返回	Strings
			 StringBuffer strBuf = new StringBuffer();  	//  strBuf 缓存字符串中已经识别的部分
			 readNext();
			 while( peek != '"'){		
				 if(peek == (char)-1)
				 {
														// 报错！！！ 引号无法匹配 [直到文件结束！也可修改条件另分析直到该行结束]
					 new Perror(Errors.LexLostQuotation).printErrorInfo(getLineno(), getWordno());
					 //peek = ' ';						// peek中保存的是文件结束信息，交给下一次词法分析去分析结束标志	
					 return null;						// 出错返回信息
				 }
				 strBuf.append(peek);
				 readNext();
			 }
			 peek = ' ';
			 String str = strBuf.toString();
			 return new Strings(str);
		 }
		 
		 if( Character.isLetter(peek)||peek == '_' ){	//  识别标识符    返回		Word
	         StringBuffer strBuf = new StringBuffer();  	//  strBuf 缓存字符串中已经识别的部分
	         do{											//  标识符只能以字母或者下划线开头
	        	 											//  此后也可以含有数字
	            strBuf.append(peek); 
	            readNext();
	         }while( Character.isLetterOrDigit(peek)|| peek == '_' || Character.isDigit(peek));
	         
	         String str = strBuf.toString();		
	         Word word = (Word)wordsTable.get(str);			//  查表判断是否为保留字
	         if( word != null )								//  是保留字则返回保留字
	        	 return word;
	         												//  否则表示是标识符，则创建一个Word对象入表， 并返回
	         word = new Word(str, Tag.ID);
	         wordsTable.put(str, word);
	         return word;
	     }
		
		 if(peek == (char)-1)
	     {
			 System.out.println();
			 System.out.println();
			 System.out.println("词法分析器读取到EOF!!!结束工作.Go to Sleep!!!");
			 return new Token(-1);
	     }
		 else{
												// 报错！！！ 读取到非法字符
			 new Perror(Errors.LexInvalidChar).printErrorInfo(getLineno(), getWordno());
			 peek = ' ';						// 便于继续分析	
			 return null;						// 出错返回信息
		 }
	 }
	 
	/**
	 *    通过的给定的filename字符串，建立rf与该文件的链接即 根据filename 打开工程路径下的目标文件
	 *    
	 * @param filename 词法分析器要打开的源程序文件名
	 * @throws java.io.FileNotFoundException
	 */
	 public void accessFile(String filename) throws java.io.FileNotFoundException{
		 rf = new FileReader(filename);
		 codeReader = new PushbackReader(rf,1);		//链接PushbackReader流  与 FileReader流
		 System.out.println("词法分析器读取流标记链接正常.........");		
	 }
	 
	 /**
	  *    读取方法，从缓冲中每次读入一个字符存储到peek中
	  *    
	  * @throws IOException
	  */
	 void readNext() throws IOException 
	 { 
		 peek = (char) codeReader.read();

	 }
	 
	 /**
	  * 	判断下一个字符是否读取到期望的字符
	  * 
	  * @param  期望下一个读到的字符
	  * @return false 未读到期望的字符; true 读的期望的字符
	  * @throws IOException
	  */
	 boolean readch(char c) throws IOException {
		 readNext();
	     if( peek != c ) return false;
	     peek = ' ';				//  便于scan()方法分析时将提前读取的字符忽略掉
	     return true;
	 }
	 
	 /**
	  *     将预留的词法单元保存到wordsTable中
	  * 
	  * @param word	预留的词法单元	保留字、特殊符号
	  */
	 void reserve(Word word) 
	 { 	
		 wordsTable.put(word.lexeme, word); 
	 }	 
	 
	 /**
	  * 	返回当前词法分析器扫描到的行号	
	  * 
	  * @return	当前词法分析器所扫描到的行号
	  */
	 public int getLineno(){
		 return curLine;
	 }
	 
	 /**
	  * 	返回当前行正在扫描的词法单元号	
	  * 
	  * @return	当前行正在扫描的词法单元号
	  */
	 public int getWordno(){
		 return curNum;
	 }
	 
	 /**
	  * 	返回当前词法分析器扫描过的词法单元的总数
	  * 
	  * @return	当前词法分析器扫描过的词法单元的总数
	  */
	 public int getTotalNum(){
		 return totalNum;
	 }
}